﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

#if LIBUSBDOTNET
using LibUsbDotNet;
using LibUsbDotNet.Main;
using LibUsbDotNet.LibUsb;
using RGiesecke.DllExport;
using System.Windows.Forms;
#else
using MadWizard.WinUSBNet;
#endif

namespace XboxOneDriver
{
    public struct XboxOneGamepadInfo
    {
        public int index;
        public string data;

        public override string ToString()
        {
            return data + " Index: " + index;
        }
    }
    public class XboxOneGamepad : IDisposable
    {
        public static XboxOneGamepad[] instances = new XboxOneGamepad[16];

        public static XboxOneGamepad GetGamepad(int index)
        {
            return instances[index];
        }

        const int kVendorMicrosoft = 0x045e;
        const int kProductXbox360Controller = 0x028e;
        const int kProductXboxOneController = 0x02d1;

        const int XBOX_ONE_STATUS_MESSAGE_BUTTONS = 0x20;
        const int XBOX_ONE_BIG_BUTTON = 7;

        private int index;
        public int Index
        {
            get { return index; }
        }

#if LIBUSBDOTNET
        public UsbDevice gamePadDevice;
        public UsbDeviceFinder gamePadFinder = new UsbDeviceFinder(kVendorMicrosoft, kProductXboxOneController);
#else
        public static USBDevice gamePadDevice;
        public static USBDeviceInfo gamePadFinder;
#endif

        public XboxOneButtonData Data
        {
            get { return buttons; }
        }

        public event Action<XboxOneGamepad, XboxOneButtonData> OnDataArrive;
        public event Action<XboxOneGamepad, bool> OnConnectedChanged;

        public static int AllGamepads
        {
            get
            {
                UsbRegDeviceList devices = UsbDevice.AllDevices;

                int count = 0;
                for (int i = 0; i < devices.Count; i++)
                {
                    UsbRegistry device = devices[i];
                    if (device.Vid == kVendorMicrosoft &&
                        device.Pid == kProductXboxOneController)
                    {
                        // gamepad
                        count++;
                    }
                }

                return count;

            }
        }

        public static XboxOneGamepadInfo[] ConnectedGamepads
        {
            get
            {
                List<XboxOneGamepadInfo> list = new List<XboxOneGamepadInfo>();
                UsbRegDeviceList devices = UsbDevice.AllLibUsbDevices;
                for (int i = 0; i < devices.Count; i++)
                {
                    LibUsbRegistry device = (LibUsbRegistry)devices[i];
                    if (device.Vid == kVendorMicrosoft &&
                        device.Pid == kProductXboxOneController)
                    {
                        XboxOneGamepadInfo info = new XboxOneGamepadInfo();
                        info.index = device.DeviceIndex;
                        info.data = device.FullName;
                        list.Add(info);
                    }
                }

                return list.ToArray();
            }
        }


        private bool connected;
        public bool Connected
        {
            get 
            {
                return connected; 
            }
            private set
            {
                if (connected != value && OnConnectedChanged != null)
                {
                    OnConnectedChanged(this, value);
                }

                connected = value;
            }
        }



        public XboxOneGamepad()
        {
            if (instances == null)
            {
                instances = new XboxOneGamepad[16];
            }
            index = 1;
            instances[index - 1] = this;

            Connect();
        }

        public XboxOneGamepad(int index)
        {
            this.index = index;
            if (instances == null)
            {
                instances = new XboxOneGamepad[16];
            }
            instances[index - 1] = this;
            Connect();
        }

        private static bool initializedAll;

        [DllExport]
        public static void InitializeAll()
        {
            if (!initializedAll)
            {
                initializedAll = true;
                for (int i = 0; i < 16; i++)
                {
                    new XboxOneGamepad(i + 1);
                }
            }
        }

        public static void DestroyAll()
        {
            if (initializedAll)
            {
                for (int i = 0; i < 16; i++)
                {
                    instances[i].Dispose();
                }
                initializedAll = false;
            }
        }

        [DllExport]
        public static short GetLeftStickY(int index)
        {
            XboxOneGamepad gamepad = instances[index];
            return gamepad == null ? (short)0 : gamepad.buttons.stick_left_y;
        }

        [return:MarshalAs(UnmanagedType.U1)]
        public static bool GetAPressed(int index)
        {
            XboxOneGamepad gamepad = instances[index];

            return gamepad == null ? false : gamepad.buttons.a;
        }

       /* [DllExport]
        public static void GetButtons(
            int index,
            out bool start,
            out bool back,
            out bool a,
            out bool b,
            out bool x,
            out bool y,
            out bool dpad_up,
            out bool dpad_down,
            out bool dpad_left,
            out bool dpad_right,
            out bool bumper_left,
            out bool bumper_right,
            out bool stick_left_click,
            out bool stick_right_click,
            out ushort trigger_left,
            out ushort trigger_right,
            out short stick_left_x,
            out short stick_left_y,
            out short stick_right_x,
            out short stick_right_y,
            out bool centerButton)
        {
            XboxOneButtonData btn = instances[index].buttons;
            start = btn.start;
            back = btn.back;
            a = btn.a;
            b = btn.b;
            x = btn.x;
            y = btn.y;
            dpad_up = btn.dpad_up;
            dpad_down = btn.dpad_down;
            dpad_left = btn.dpad_left;
            dpad_right = btn.dpad_right;
            bumper_left = btn.bumper_left;
            bumper_right = btn.bumper_right;
            stick_left_click = btn.stick_left_click;
            stick_right_click = btn.stick_right_click;
            trigger_left = btn.trigger_left;
            trigger_right = btn.trigger_right;
            stick_left_x = btn.stick_left_x;
            stick_left_y =  btn.stick_left_y;
            stick_right_x = btn.stick_right_x;
            stick_right_y = btn.stick_right_y;
            centerButton = btn.centerButton;
        }*/

        public void Connect()
        {
            if (connected)
            {
                return;
            }

            // (Microsoft)
            // MICROSOFT VID: 045E (1118)
            // MY MOUSE: 00CB
            // MY KEYBOARD: 0750
            // XBOX ONE GAMEPAD: 0x02d1

            // (Shit keyboard and Mouse I'm using now) (it hurts to type) (I paid 7 bucks on this shit)
            // MOUSE:    VID_1C4F (7247)  PID_0002 (   2)
            // KEYBOARD: VID_15D9 (5593)  PID_0A4C (2636)

#if LIBUSBDOTNET
            try
            {
                // Find and open the usb device.
                // get all USB devices

                UsbRegDeviceList devices = UsbDevice.AllLibUsbDevices;

                for (int i = 0; i < devices.Count; i++)
                {
                    LibUsbRegistry device = (LibUsbRegistry)devices[i];
                    if (device.Vid == kVendorMicrosoft &&
                        device.Pid == kProductXboxOneController &&
                        device.DeviceIndex == index)
                    {
                        gamePadDevice = device.Device;
                    }
                }



                //gamePadDevice = UsbDevice.OpenUsbDevice(gamePadFinder);

                // If the device is open and ready
                if (gamePadDevice == null)
                {
                    Connected = false;
                    stop = true;
                    return;
                }

                // If this is a "whole" usb device (libusb-win32, linux libusb)
                // it will have an IUsbDevice interface. If not (WinUSB) the 
                // variable will be null indicating this is an interface of a 
                // device.
                IUsbDevice wholeUsbDevice = gamePadDevice as IUsbDevice;
                if (!ReferenceEquals(wholeUsbDevice, null))
                {
                    // This is a "whole" USB device. Before it can be used, 
                    // the desired configuration and interface must be selected.

                    // Select config #1
                    wholeUsbDevice.SetConfiguration(1);

                    // Claim interface #0.
                    wholeUsbDevice.ClaimInterface(0);
                }

                UsbEndpointReader.DefReadBufferSize = 512;
                // open read endpoint 1.
                reader = gamePadDevice.OpenEndpointReader(ReadEndpointID.Ep01);
                // open write endpoint 1.
                UsbEndpointWriter writer = gamePadDevice.OpenEndpointWriter(WriteEndpointID.Ep01);
                int bytesWritten;
                writer.Write(new byte[] { 0x05, 0x20 }, 1000, out bytesWritten);
                writer.Dispose();

                // start reading thread
                stop = false;
                Connected = true;

                ThreadPool.QueueUserWorkItem(ReceiveData);
            }
            catch { }
#endif
        }

        private UsbEndpointReader reader;
        private bool stop = false;
        private XboxOneButtonData buttons;
        private const int timeout = 20;
        private byte[] buffer = new byte[64];

        void ReceiveData(object data)
        {
            while (!stop)
            {
                int read;
                ErrorCode code = reader.Read(buffer, timeout, out read);
                if (code == ErrorCode.IoTimedOut)
                {
                    continue;
                }
                ProcessData(buffer);
            }
        }

        void reader_DataReceived(object sender, EndpointDataEventArgs e)
        {
            if (e.Count < 2)
            {
                return;
            }

            UsbEndpointReader reader = (UsbEndpointReader)sender;
            byte[] readBuffer = e.Buffer;
            ProcessData(readBuffer);
        }

        private void ProcessData(byte[] readBuffer)
        {
            byte type = readBuffer[0];
            if (type == XBOX_ONE_BIG_BUTTON)
            {
                buttons.centerButton = readBuffer[4] == 1 ? true : false;
                if (OnDataArrive != null)
                {
                    OnDataArrive(this, buttons);
                }
                return;
            }
            if (type != XBOX_ONE_STATUS_MESSAGE_BUTTONS)
            {
                return;
            }

            buttons = new XboxOneButtonData();

            byte b = readBuffer[4];
            buttons.sync = (b & (1 << 1 - 1)) != 0;
            buttons.dummy1 = (b & (1 << 2 - 1)) != 0;
            buttons.start = (b & (1 << 3 - 1)) != 0;
            buttons.back = (b & (1 << 4 - 1)) != 0;

            buttons.a = (b & (1 << 5 - 1)) != 0;
            buttons.b = (b & (1 << 6 - 1)) != 0;
            buttons.x = (b & (1 << 7 - 1)) != 0;
            buttons.y = (b & (1 << 8 - 1)) != 0;

            b = readBuffer[5];
            buttons.dpad_up = (b & (1 << 1 - 1)) != 0;
            buttons.dpad_down = (b & (1 << 2 - 1)) != 0;
            buttons.dpad_left = (b & (1 << 3 - 1)) != 0;
            buttons.dpad_right = (b & (1 << 4 - 1)) != 0;

            buttons.bumper_left = (b & (1 << 5 - 1)) != 0;
            buttons.bumper_right = (b & (1 << 6 - 1)) != 0;
            buttons.stick_left_click = (b & (1 << 7 - 1)) != 0;
            buttons.stick_right_click = (b & (1 << 8 - 1)) != 0;

            buttons.trigger_left = BitConverter.ToUInt16(readBuffer, 6);
            buttons.trigger_right = BitConverter.ToUInt16(readBuffer, 8);

            buttons.stick_left_x = BitConverter.ToInt16(readBuffer, 10);
            buttons.stick_left_y = BitConverter.ToInt16(readBuffer, 12);
            buttons.stick_right_x = BitConverter.ToInt16(readBuffer, 14);
            buttons.stick_right_y = BitConverter.ToInt16(readBuffer, 16);

            if (OnDataArrive != null)
            {
                OnDataArrive(this, buttons);
            }
        }


        public void Dispose()
        {
#if LIBUSBDOTNET
            stop = true;
            Thread.Sleep(timeout * 2); // wait for the IO operation to finish

            if (gamePadDevice != null)
            {
                if (gamePadDevice.IsOpen)
                {
                    // If this is a "whole" usb device (libusb-win32, linux libusb-1.0)
                    // it exposes an IUsbDevice interface. If not (WinUSB) the 
                    // 'wholeUsbDevice' variable will be null indicating this is 
                    // an interface of a device; it does not require or support 
                    // configuration and interface selection.
                    IUsbDevice wholeUsbDevice = gamePadDevice as IUsbDevice;
                    if (!ReferenceEquals(wholeUsbDevice, null))
                    {
                        // Release interface #0.
                        wholeUsbDevice.ReleaseInterface(0);
                    }

                    gamePadDevice.Close();
                }
                gamePadDevice = null;

                // Free usb resources
                UsbDevice.Exit();
            }
#endif
        }
    }
}
